package com.framework.component.resolver.handlebars;

import com.framework.component.resolver.Resolver;
import com.framework.component.resolver.reflect.FieldMember;
import com.framework.component.resolver.reflect.FieldWrapper;
import com.framework.component.resolver.register.ResolverRepository;
import com.github.jknack.handlebars.ValueResolver;
import com.github.jknack.handlebars.context.JavaBeanValueResolver;
import org.springframework.stereotype.Component;

import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

@Component
public class HandlebarsValueResolver implements ValueResolver {

    private final ResolverRepository resolverRepository;

    private final Map<Class<?>, Map<String, FieldWrapper>> cache = new ConcurrentHashMap<>();

    public HandlebarsValueResolver(ResolverRepository resolverRepository) {
        this.resolverRepository = resolverRepository;
    }

    @Override
    public Object resolve(Object data, String name) {
        Object value = JavaBeanValueResolver.INSTANCE.resolve(data, name);
        if (value != null) {
            return value;
        }

        Map<String, FieldWrapper> mcache = cache(data.getClass());
        FieldWrapper member = mcache.get(name);
        if (member == null) {
            return null;
        }

        if (!resolverRepository.getResolverRepos().containsKey(member.annoResolver().value())) {
            return null;
        }
        com.framework.component.resolver.ValueResolver valueResolver = resolverRepository.getResolverRepos().get(member.annoResolver().value());
        Object resolveVal = valueResolver.resolve(data);
        setMember(member, data, resolveVal);
        return resolveVal;
    }

    @Override
    public Object resolve(Object data) {
        return JavaBeanValueResolver.INSTANCE.resolve(data);
    }

    @Override
    public Set<Map.Entry<String, Object>> propertySet(Object data) {
        return JavaBeanValueResolver.INSTANCE.propertySet(data);
    }

    private Map<String, FieldWrapper> cache(final Class<?> clazz) {
        Map<String, FieldWrapper> mcache = this.cache.get(clazz);
        if (mcache != null) {
            return mcache;
        }

        mcache = new HashMap<>();
        Set<FieldWrapper> members = members(clazz);
        for (FieldWrapper m : members) {
            ((AccessibleObject) m).setAccessible(true);
            mcache.put(memberName(m), m);
        }
        this.cache.put(clazz, mcache);
        return mcache;
    }

    private String memberName(FieldWrapper m) {
        return m.getName();
    }

    private Set<FieldWrapper> members(final Class<?> clazz) {
        Set<FieldWrapper> members = new LinkedHashSet<>();
        if (clazz.isArray()) {
            return null;
        } else {
            Class<?> targetClass = clazz;
            do {
                Field[] fields = targetClass.getDeclaredFields();
                for (Field field : fields) {
                    Resolver resolver = field.getAnnotation(Resolver.class);
                    if (resolver != null) {
                        members.add(new FieldMember(field, resolver));
                    }
                }
                targetClass = targetClass.getSuperclass();
            } while (targetClass != null && targetClass != Object.class);
        }
        return members;
    }

    private void setMember(final FieldWrapper field, final Object context, final Object value) {
        try {
            field.set(context, value);
        } catch (Exception ex) {
            throw new IllegalStateException(
                    "Shouldn't be illegal to access field '" + field.getName()
                            + "'", ex);
        }
    }
}
